package com.nwcl.ysx.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.nwcl.basic.bo.OrderFlowBO;
import com.nwcl.basic.bo.OrdersBO;
import com.nwcl.basic.service.OrderFlowService;
import com.nwcl.basic.service.OrdersService;
import com.nwcl.common.constant.Constant;
import com.nwcl.common.result.Result;
import com.nwcl.common.utils.MD5;
import com.nwcl.common.utils.wx.MyConfig;
import com.nwcl.common.utils.wx.WXPay;
import com.nwcl.ysx.request.PayRequest;
import org.apache.commons.codec.binary.Hex;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.math.BigDecimal;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;

/*
 * @Description: 支付接口
 * @Author liguiqin
 * @Date 2023/5/24 9:17
 * @param: null
 * @return
 * @UpdateDate 2023/5/24 9:17
 **/
@RestController
@RequestMapping("/api/ysx")

public class ApiPayController {
    @Value("${ysx.mchkey}")
    private String mchKey;
    @Value("${ysx.appid}")
    private String appId;
    @Value("${ysx.mchid}")
    private String mchId;
    @Value("${ysx.notify.url}")
    private String notifyUrl;
    private Logger log = LogManager.getLogger(ApiPayController.class);

    @Autowired
    private OrdersService ordersService;
    @Autowired
    private OrderFlowService orderFlowService;

    /**
     * 微信统一下单
     *
     * @return
     * @throws Exception
     */
    @PostMapping(value = "/pay")
    public Result pay(@RequestBody PayRequest param) throws Exception {
        Result rs = param.check();
        if (rs != null) {
            return rs;
        }
        String orderCode = param.getOrderCode();
        BigDecimal price = param.getPrice();
        String body = param.getBody();
        OrdersBO ordersBO = ordersService.getOrderByCode(orderCode);
        if (ordersBO == null) {
            return Result.fail("订单已失效");
        }
        if (ordersBO.getPrice().compareTo(price) != 0) {
            return Result.fail("订单金额不一致");
        }
        if (ordersBO.getStatus() != Constant.OrderStatus.pend) {
            return Result.fail("订单不可支付");
        }
        MyConfig config = new MyConfig(appId, mchId, mchKey);
        WXPay wxpay = new WXPay(config);
        Map<String, String> data = new LinkedHashMap<String, String>();
        data.put("body", body);
        data.put("out_trade_no", orderCode);
        data.put("fee_type", "CNY");
        data.put("total_fee", price.toString()); // 分单位
        data.put("notify_url", notifyUrl);
        data.put("trade_type", "NATIVE");  // "JSAPI"
        Map<String, String> resp = new HashMap<>();
        try {
            resp = wxpay.unifiedOrder(data);
            if (resp.get("err_code_des") != null) {
                log.error("微信统一下单异常:" + JSON.toJSONString(resp));
                return Result.fail("微信统一下单异常", resp);
            }
            log.info("微信统一下单:" + JSON.toJSONString(resp));
            return Result.success(resp);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return Result.fail("微信统一下单异常", resp);
    }

    /**
     * 微信支付结果回调通知
     *
     * @throws Exception
     */
    @PostMapping(value = "/payNotify")
    public String payNotify(HttpServletRequest request) throws Exception {
        Map<String, String> wxBackData = new HashMap<String, String>();
        InputStream is = request.getInputStream();
        String xml = inputStream2String(is, "UTF-8");
        // 后面把xml转成Map根据数据作逻辑处理
        Map<String, String> map = new HashMap<>();
        if (null == xml || xml.length() == 0) {
            wxBackData.put("return_code", "ERROR");
            wxBackData.put("return_msg", "支付回调参数为空");
            return mapToXml(wxBackData);
        }
        map = xmlToMap(xml);
        log.info("支付结果回调返回信息: " + JSONObject.toJSONString(map));
        if (!"success".equalsIgnoreCase(map.get("result_code"))) {
            wxBackData.put("return_code", "ERROR");
            wxBackData.put("return_msg", "支付参数错误");
            return mapToXml(wxBackData);
        }
        String sign = map.get("sign");
        //微信支付校验,验证是否来自微信
        SortedMap<String, String> signMap = new TreeMap<>();
        for (Map.Entry<String, String> entry : map.entrySet()) {
            signMap.put(entry.getKey(), entry.getValue());
        }
        signMap.remove("sign");
        String checkSign = this.sign(signMap, mchKey, false);
        log.error("微信回调支付, 本地验证checkSign:" + checkSign);
        if (!sign.equalsIgnoreCase(checkSign)) {
            wxBackData.put("return_code", "ERROR");
            wxBackData.put("return_msg", "参数验证失败");
            log.error("参数验证失败");
            return mapToXml(wxBackData);
        }
        String orderCode = map.get("out_trade_no");//商户订单号
        String transactionId = map.get("transaction_id");//微信订单号
        //处理订单支付成功后的业务逻辑
        OrdersBO ordersBO = ordersService.getOrderByCode(orderCode);
        if (ordersBO == null || ordersBO.getStatus() != Constant.OrderStatus.pend) {
            log.error("支付回调订单不存在或者订单状态不是待支付:" + orderCode);
            wxBackData.put("return_code", "ERROR");
            wxBackData.put("return_msg", "支付回调订单不存在或者订单状态不是待支付");
            return mapToXml(wxBackData);
        }
        //更新订单状态
        Date payTime=new Date();
        ordersService.updateOrderStatus(orderCode, Constant.OrderStatus.pay,payTime);
        //保存订单支付流水
        OrderFlowBO flowBO=new OrderFlowBO();
        flowBO.setId(ordersBO.getId());
        flowBO.setPrice(ordersBO.getPrice());
        flowBO.setRealPrice(ordersBO.getRealPrice());
        flowBO.setThirdOrderCode(transactionId);
        flowBO.setThirdType(Constant.OrderFlowThirdType.wx);
        flowBO.setType(Constant.OrderFlowType.out);
        flowBO.setCreateTime(new Date());
        flowBO.setUpdateTime(new Date());
        flowBO.setIsValid(Constant.isValid.valid);
        orderFlowService.save(flowBO);
        //返回微信支付成功信息
        wxBackData.put("return_code", "SUCCESS");
        wxBackData.put("return_msg", "回调成功");
        return mapToXml(wxBackData);
    }


    /**
     *  * InputStream流转换成String字符串
     *  * @param inStream InputStream流
     *  * @param encoding 编码格式
     *  * @return String字符串
     *  
     */
    private static String inputStream2String(InputStream inStream, String encoding) {
        String result = null;
        try {
            if (inStream != null) {
                ByteArrayOutputStream outStream = new ByteArrayOutputStream();
                byte[] tempBytes = new byte[1024];
                int count = -1;
                while ((count = inStream.read(tempBytes, 0, 1024)) != -1) {
                    outStream.write(tempBytes, 0, count);
                }
                outStream.flush();
                result = new String(outStream.toByteArray(), encoding);
            }
        } catch (Exception e) {
            result = null;
        }
        return result;
    }

    /**
     * 将Map转换为XML格式的字符串
     *
     * @param data Map类型数据
     * @return XML格式的字符串
     * @throws Exception
     */
    public static String mapToXml(Map<String, String> data) throws Exception {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
        org.w3c.dom.Document document = documentBuilder.newDocument();
        org.w3c.dom.Element root = document.createElement("xml");
        document.appendChild(root);
        for (String key : data.keySet()) {
            String value = data.get(key);
            if (value == null) {
                value = "";
            }
            value = value.trim();
            org.w3c.dom.Element filed = document.createElement(key);
            filed.appendChild(document.createTextNode(value));
            root.appendChild(filed);
        }
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer = tf.newTransformer();
        DOMSource source = new DOMSource(document);
        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        StringWriter writer = new StringWriter();
        StreamResult result = new StreamResult(writer);
        transformer.transform(source, result);
        String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
        try {
            writer.close();
        } catch (Exception ex) {
        }
        return output;
    }

    /**
     * XML格式字符串转换为Map
     *
     * @param strXML XML字符串
     * @return XML数据转换后的Map
     * @throws Exception
     */
    public static Map<String, String> xmlToMap(String strXML) throws Exception {
        String FEATURE = "";
        try {
            Map<String, String> data = new HashMap<String, String>();
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            documentBuilderFactory.setXIncludeAware(false);
            documentBuilderFactory.setExpandEntityReferences(false);
            FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
            documentBuilderFactory.setFeature(FEATURE, true);  //防止可能的SSRF
            FEATURE = "http://xml.org/sax/features/external-parameter-entities";
            documentBuilderFactory.setFeature(FEATURE, false);
            FEATURE = "http://xml.org/sax/features/external-general-entities";
            documentBuilderFactory.setFeature(FEATURE, false);
            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
            InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
            org.w3c.dom.Document doc = documentBuilder.parse(stream);
            doc.getDocumentElement().normalize();
            NodeList nodeList = doc.getDocumentElement().getChildNodes();
            for (int idx = 0; idx < nodeList.getLength(); ++idx) {
                Node node = nodeList.item(idx);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    org.w3c.dom.Element element = (org.w3c.dom.Element) node;
                    data.put(element.getNodeName(), element.getTextContent());
                }
            }
            try {
                stream.close();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            return data;
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return new HashMap<>();
    }

    /**
     * 微信统一下单
     *
     * @param map
     * @param mchKey
     * @param isSha
     * @return
     * @throws InvalidKeyException
     * @throws NoSuchAlgorithmException
     */
    public String sign(SortedMap<String, String> map, String mchKey, boolean isSha) throws InvalidKeyException, NoSuchAlgorithmException {
        StringBuffer stringBuffer = new StringBuffer();
        for (Map.Entry<String, String> objectObjectEntry : map.entrySet()) {
            stringBuffer.append(objectObjectEntry.getKey()).append("=").append(objectObjectEntry.getValue()).append("&");
        }
        stringBuffer = stringBuffer.deleteCharAt(stringBuffer.length() - 1);
        stringBuffer.append("&key=").append(mchKey);
        String s = MD5.MD5Encode(stringBuffer.toString()).toUpperCase();
        if (isSha) {
            Mac sha256 = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKeySpec = new SecretKeySpec(mchKey.getBytes(), "HmacSHA256");
            sha256.init(secretKeySpec);
            byte[] bytes = sha256.doFinal(s.getBytes());
            return Hex.encodeHexString(bytes).toUpperCase();
        }

        return s;
    }
}
